package com.jwong.auth.server.config;

import com.jwong.auth.server.mapper.UserAuthorityMapper;
import com.jwong.auth.server.model.PermissionDO;
import com.jwong.auth.server.model.UserDO;
import com.jwong.auth.server.model.UserModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserAuthorityMapper userAuthorityMapper;

    @Autowired
    private SmsCodeSecurityConfiguration smsCodeSecurityConfiguration;

    @Autowired
    @Qualifier("clientDetailsDataSource")
    private DataSource clientDetailsDataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/login**", "/me**", "/sms-code/**", "/sms-login", "/actuator/**", "/user/token**").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin().loginPage("/login")
        .and()
            .httpBasic()
         .and().apply(smsCodeSecurityConfiguration)
         .and()
            .rememberMe()
                .tokenValiditySeconds(7 * 24 * 60 * 60)
                .userDetailsService(userDetailsService())
                .tokenRepository(persistentTokenRepository())
         .and()
            .headers().addHeaderWriter((request, response) -> {
                response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域
                response.setHeader("Cache-Control","no-cache");
                if (request.getMethod().equals("OPTIONS")) {//如果是跨域的预检请求，则原封不动向下传达请求头信息
                    response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
                    response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
                }
            })
        .and()
            .csrf()
            .disable()
        ;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/webjars/**","/static/**")
        ;
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(clientDetailsDataSource);
        // 仅用于初始化时构建persistent_logins表
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Override
    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean(name = {BeanIds.USER_DETAILS_SERVICE})
    public UserDetailsService userDetailsService() {
        return username -> {
            UserDO userDO = Optional.of(this.userAuthorityMapper.selectUserAuthority(username)).<UsernameNotFoundException>orElseThrow(() -> {
                log.error("username：[{}] login fail , username not found.", username);
                throw new UsernameNotFoundException("UsernameNotFoundException");
            });
            Set<SimpleGrantedAuthority> authorities = new HashSet<>();
            Optional.ofNullable(userDO.getRoles()).ifPresent(roles -> roles.forEach(roleDO -> {
                Optional.ofNullable(roleDO.getRoleName()).ifPresent(roleName ->  authorities.add(new SimpleGrantedAuthority(roleName)));
                Optional.ofNullable(roleDO.getPermissions()).ifPresent(permissionDOS ->
                    permissionDOS.parallelStream().map(PermissionDO::getPermission).filter(StringUtils::hasText).forEach(permission ->
                        Optional.of(permission).map(SimpleGrantedAuthority::new).map(authorities::add)));
            }));
            log.info("username:[{}] has authorities: {}.", username, authorities.toArray());
            return new UserModel(StringUtils.hasText(userDO.getUsername())? userDO.getUsername() : username,
                    userDO.getPassword(), authorities, userDO.getUserId(), userDO.getSchoolCode());
        };
    }
}